001    package net.sf.xdc.util;
002    
003    /*
004     *  Copyright 2005-2006 Jens Voß.
005     *
006     *  Licensed under the GNU Lesser General Public License (the "License");
007     *  you may not use this file except in compliance with the License.
008     *  You may obtain a copy of the License at
009     *
010     *       http://opensource.org/licenses/lgpl-license.php
011     *
012     *  Unless required by applicable law or agreed to in writing, software
013     *  distributed under the License is distributed on an "AS IS" BASIS,
014     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     *  See the License for the specific language governing permissions and
016     *  limitations under the License.
017     */
018    
019    import java.util.StringTokenizer;
020    import java.util.regex.Pattern;
021    import java.io.FilenameFilter;
022    import java.io.File;
023    
024    /**
025     * This class implements the <code>FilenameFilter</code> interface and is used
026     * to filter out files from a list of files contained in a directory.
027     *
028     * @author Jens Voß
029     * @since 0.5
030     * @version 0.5
031     */
032    public class PathDescriptor implements FilenameFilter {
033    
034      /**
035       * This utility method is used to convert an expression containing "basic"
036       * wildcards like "?" (any character), "*" (any sequence of characters
037       * except a path separator), "**" (any seqence of directory names contained
038       * in each other) to a regular expression.
039       *
040       * @param str The string to be converted to a regular expression
041       * @return A regular expression corresponding to the string with "basic"
042       *         wildcards
043       */
044      public static String convert(String str) {
045        StringBuffer retVal = new StringBuffer();
046        char[] chars = str.toCharArray();
047        for (int i = 0; i < chars.length; i++) {
048          char c = chars[i];
049          if (c == '.') {
050            retVal.append("\\.");
051          }
052          else if (c == '+') {
053            retVal.append("\\+");
054          }
055          else if (c == '?') {
056            retVal.append('.');
057          }
058          else if (c == '*') {
059            if (i < chars.length - 1 && chars[i+1] == '*') {
060              retVal.append(".*");
061              i++;
062            }
063            else {
064              retVal.append("[^/]*");
065            }
066          }
067          else {
068            retVal.append(c);
069          }
070        }
071        return retVal.toString();
072      }
073    
074      private String regex;
075      private boolean defaultExcludes;
076      private PathDescriptor childDescriptor;
077    
078      private PathDescriptor(StringTokenizer tok, boolean defaultExcludes) {
079        this.defaultExcludes = defaultExcludes;
080        regex = convert(tok.nextToken());
081        if (tok.hasMoreTokens()) {
082          childDescriptor = new PathDescriptor(tok, defaultExcludes);
083        }
084      }
085    
086      /**
087       * Public constructor.
088       *
089       * @param path A path pattern. Its constituents are delimited by forward
090       *        slashes ('/') and may contain "basic" wildcards: An asterisk ("*")
091       *        means "any substring" (even an empty substring); a question mark
092       *        ("?") means "any character".
093       * @param defaultExcludes Specifies whether directories or files probably
094       *        containing version control information should be excluded from
095       *        processing
096       */
097      public PathDescriptor(String path, boolean defaultExcludes) {
098        this(new StringTokenizer(path, "/", false), defaultExcludes);
099      }
100    
101      /**
102       * This is an implementation of the method defined in the
103       * <code>FilenameFilter</code> interface. It tests whether a file with a
104       * certain name is included in the list passing through this filter.
105       *
106       * @param dir The directory in which the file is found
107       * @param name The name of the file
108       * @return <b>true</b> if the file should be included in the list (in this
109       *         case if and only if the file name matches the pattern of the first
110       *         constituent of the path used for construction); <b>false</b>
111       *         otherwise
112       */
113      public boolean accept(File dir, String name) {
114        File file = new File(dir, name);
115        return matches(file) || matchesDirWithWildcard(file);
116      }
117    
118      /**
119       * This method determines whether a file is a directory and the path pattern
120       * of this <code>PathDescriptor</code> is the path wildcard pattern ("**").
121       *
122       * @param file The file to be tested
123       * @return <b>true</B> if this <code>PathDescriptor</code>'s path pattern is
124       *         "**" and the file exists and is a directory and is not to be
125       *         excluded because it might be a version control directory.
126       */
127      public boolean matchesDirWithWildcard(File file) {
128        if (!".*.*".equals(regex)
129            || (defaultExcludes && IOUtils.isDefaultExclude(file.getName()))) {
130          return false;
131        }
132        return file.exists() && file.isDirectory();
133      }
134    
135      /**
136       * This method determines whether a String matches the pattern expressed by
137       * the first constituent of the path used for construction of this
138       * <code>PathDescriptor</code>.
139       *
140       * @param file The <code>File</code> whose name is to be compared to the
141       *        pattern of this <code>PathDescriptor</code>
142       * @return <b>true</b> if the String matches the pattern; <b>false</b>
143       *         otherwise
144       */
145      public boolean matches(File file) {
146        return !regex.equals(".*.*") && Pattern.matches(regex, file.getName());
147      }
148    
149      /**
150       * If the path used for construction of this <code>PathDescriptor</code>
151       * consists of more than one constituent, this method can be used to retrieve
152       * a child descriptor corresponding to the sub-path after the first
153       * constituent.
154       *
155       * @return The sub-path after the first constituent of the path used for
156       *         constructing this <code>PathDescriptor</code>. If that path only
157       *         consisted of one such part, the method returns <b>null</b>.
158       */
159      public PathDescriptor getChildDescriptor() {
160        return childDescriptor;
161      }
162    
163    }